pub suberror LexerError String derive(Show)
pub suberror ParseError String derive(Show)

pub enum Token {
  NUMBER(Int)
  ADD
  SUB
  MUL
  DIV
  RETURN
  TERM
  EOF
} derive(Show,Eq)

pub fn lexer(code: String) -> Array[Token] raise {
  let tokens = Array::new()
  loop code[:] {
    [] => {
      tokens.push(EOF)
      break tokens
    }
    [.. "return" , .. rest] => {
      tokens.push(RETURN)
      continue rest
    }
    [';', .. rest] => {
      tokens.push(TERM)
      continue rest
    }
    ['0'..='9', ..] as code => {
      let (tok, rest) = lex_number(code)
      tokens.push(tok)
      continue rest
    }
    ['+' | '-' | '*' | '/' as math_operation, .. rest] => {
      let token = match math_operation {
        '+' => ADD
        '-' => SUB
        '*' => MUL
        '/' => DIV
        _ => raise LexerError("Parse Error: Unexpected math operation: [\{math_operation}]")
      }
      tokens.push(token)
      continue rest
    }
    [' ' | '\n' | '\r' | '\t', .. rest] => continue rest
    other_strs =>
      raise LexerError("Parse Error: Unexpected char: [\{other_strs}]")
  }
}

///|
pub fn lex_number(rest : @string.View) -> (Token, @string.View) raise {
  let num_chars = Array::new()
  loop rest {
    ['0'..='9' as c, .. rest_str] => {
      num_chars.push(c)
      continue rest_str
    }
    _ as rest => {
      let num_str = String::from_array(num_chars)
      let num = match (try? @strconv.parse_int(num_str)) {
        Ok(n) => n
        Err(_) => raise LexerError("Parse Integer failed: \{num_str}")
      }
      break (NUMBER(num), rest)
    }
  }
}

pub fn parse(tokens: Array[Token]) -> Array[Node] raise {
  let nodes: Array[Node] = Array::new()
  let control = StartNode::make()
  nodes.push(control)
  loop tokens[:] {
    [EOF] => {
      break nodes
    }
    [RETURN, .. other] => {
      let (expr, rest) = parseExpr(other)
      nodes.push(ReturnNode::make(control, expr))
      continue rest
    }
    [TERM, .. rest] => {
      continue rest
    }
    _ as x => {
      raise ParseError("Unexpected token \{x}")
    }
  }
}

fn parseAddition(tokens: ArrayView[Token]) -> (Node, ArrayView[Token]) raise {
  let (lhs, rest) = parseMultiplication(tokens)
  match rest {
    [ADD, .. tail] => {
      let (rhs, tail) = parseAddition(tail)
      (AddNode::make(lhs, rhs), tail)
    }
    [SUB, .. tail] => {
      let (rhs, tail) = parseAddition(tail)
      (SubNode::make(lhs, rhs), tail)
    }
    _ => (lhs, rest)
  }
}

fn parseMultiplication(tokens: ArrayView[Token]) -> (Node, ArrayView[Token]) raise {
  let (lhs, rest) = parseUnary(tokens)
  match rest {
    [MUL, .. tail] => {
      let (rhs, tail) = parseMultiplication(tail)
      (MulNode::make(lhs, rhs), tail)
    }
    [DIV, .. tail] => {
      let (rhs, tail) = parseMultiplication(tail)
      (DivNode::make(lhs, rhs), tail)
    }
    _ => (lhs, rest)
  }
}

fn parseUnary(tokens: ArrayView[Token]) -> (Node, ArrayView[Token]) raise {
  match tokens {
    [NUMBER(val), .. tail] => {
      (ConstantNode::make(val), tail)
    }
    [SUB, NUMBER(val), .. tail] => {
      (ConstantNode::make(-val), tail)
    }
    _ as x => {
      raise ParseError("Parse expression error \{x}")
    }
  }
}

fn parseExpr(tokens: ArrayView[Token]) -> (Node, ArrayView[Token]) raise {
  match tokens {
    [NUMBER(val), .. rest] => {
      (ConstantNode::make(val), rest)
    }
    _ as x => {
      raise ParseError("Parse expression error \{x}")
    }
  }
}

test "lex(\"return 10;\"" {
  let tokens = lexer("return 10;")
  assert_eq(tokens.length(), 4)
  assert_eq(tokens[0], RETURN)
  assert_eq(tokens[1], NUMBER(10))
  assert_eq(tokens[2], TERM)
  assert_eq(tokens[3], EOF)
}

test "parse(\"return 10;\"" {
  let tokens = lexer("return 10;")
  let nodes = parse(tokens)
  assert_eq(nodes.length(), 2)
  assert_eq(nodes[0].class(), Start)
  assert_eq(nodes[1].class(), Return)
}

test "parse only EOF" {
  let tokens = [EOF]
  let nodes = parse(tokens)
  assert_eq(nodes.length() , 1)
  assert_eq(nodes[0].class() , Start)
}
